/*!
 * avangard/chat/display.js
 * © 2009 avangardonline.ru
 * @author BlooDHounD <http://www.controller.by>
 */

if ( !window.controller ) throw new Error( '"controller" not initialized' );

var display = ( function() {

	// shortcut 
	var $ =	controller;

	$.require( 'controller.events.EventDispatcher' );
	$.require( 'controller.events.dom' );
	$.require( 'controller.template' );
	$.require( 'controller.utils' );
	$.require( 'avangard.Character' );
	$.require( 'avangard.chat.Channel' );
	$.require( 'avangard.chat.Message' );

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	var	win =		window,
		doc =		win.document,

		browser =	$.browser,
		events =	$.events,
		EEvent =	events.Event,
		dom =		events.dom,
		utils =		$.utils,
		template =	$.template,

		msie =		browser.getMSIE(),
		opera =		browser.getOpera(),

		UPDATE =	'update',
		RESIZE =	'resize',
		E_A =		'added',
		E_R =		'removed',
		HE =		'handler_',
		HE_A =		HE + E_A,
		HE_R =		HE + E_R,

		_regs =		/[\(\)\[\]\\\.\*\+\?]/g,
		_spaces =	/\s+/g,
		_empty =	/^\s*$/,
		_smile2 =	/[=\-о]/,
		_smile3 =	/[\)\(\*DPР0ОOS$3EЕВB]/,
		_nick =		/^\s*\[\s*([^\]]+?)\s*\]/,
		_uri =		/^([\w\d]+\.)*townwars\.ru(\/[^\s]*)?/,

		_state =			null,
		_chatData =			null,
		_selectedChannel =	null,

		tpl_retry =				template.loadTemplate( '/tpl/retry.tpl' ),
		tpl_wait =				template.loadTemplate( '/tpl/wait.tpl' ),
		tpl_content =			template.getTemplate( 'tpl_chatContent' ),
		tpl_message =			template.getTemplate( 'tpl_chatMessage' ),
		tpl_messageTime =		template.getTemplate( 'tpl_chatMessageTime' ),
		tpl_messageContent =	template.getTemplate( 'tpl_chatMessageContent' ),
		tpl_link =				template.getTemplate( 'tpl_chatLink' ),
		tpl_textSmile =			template.getTemplate( 'tpl_chatTextSmile' ),
		tpl_smile =				template.getTemplate( 'tpl_chatSmile' ),
		tpl_characterSender =	template.getTemplate( 'tpl_chatCharacterSender' ),
		tpl_characterTo =		template.getTemplate( 'tpl_chatCharacterTo' ),
		tpl_characterPrivate =	template.getTemplate( 'tpl_chatCharacterPrivate' ),
		tpl_character =			template.loadTemplate( '/tpl/character_name.tpl' ),
		tpl_channel =			template.getTemplate( 'tpl_chatChannel' );

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 * @type	{HTMLElement}
	 */
	var app = doc.getElementById( 'chatDisplay' );

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	/**
	 * @property
	 * @type	{controller.events.EventDispatcher}
	 */
	app.dispatcher = new events.EventDispatcher( app );

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 * @method
	 * @return	{String}
	 */
	app.getState = function() {
		return _state;
	};

	/**
	 * @method
	 * @param	{String}	value
	 */
	app.setState = function(value) {
		if ( _state == value ) return;
		if ( _chatData ) {
			var data = _chatData;
			this.setData( null );
			_state = value;
			this.setData( data );
		} else {
			_state = value;
			updateState();
		}
	};

	/**
	 * method
	 * @return	{controller.utils.Hash}
	 */
	app.getData = function() {
		return _chatData;
	};

	/**
	 * method
	 * @param	{controller.utils.Hash}	value
	 */
	app.setData = function(value) {
		if ( _chatData === value ) return;

		var old = _chatData;
		_chatData = value;

		var	channelList =		getChannelList(),
			channelSelect =		getChannelSelect();
		if ( channelList )		channelList.setData( _chatData ? _chatData.channels : null );
		if ( channelSelect )	channelSelect.setData( _chatData ? _chatData.channelHeaders : null );

		if ( !_chatData || !old ) {
			updateState();
		}

		// отображает дефолтный канал
		this.setChannel( null );
	};

	/**
	 * @method
	 * @return	{avangard.chat.Channel}
	 */
	app.getChannel = function() {
		return _selectedChannel;
	};

	/**
	 * @method
	 * @param	{avangard.chat.Channel}		value
	 */
	app.setChannel = function(value) {
		if ( !value && _chatData ) {
			var	arr = _chatData.channels.getValues();
			if ( arr.length > 0 ) value = arr[0];
		}
		if ( _selectedChannel === value ) return;
		_selectedChannel = value;
		// соберём объекты
		var	channelList =		getChannelList(),
			channelSelect =		getChannelSelect(),
			characterList =		getCharacterList(),
			messageList =		getMessageList();
		// обвноим занятые окошки
		if ( channelList )		channelList.select( _selectedChannel );
		if ( channelSelect )	channelSelect.select( _selectedChannel );
		if ( characterList )	characterList.setData( _selectedChannel );
		if ( messageList )		messageList.setData( _selectedChannel ? _selectedChannel.logger : null );
	};

	/**
	 * @method
	 * @param	{String}	name
	 * @param	{Boolean}	prv
	 */
	app.characterToMessage = function(name, prv) {
		characterToMessage( name, prv );
	};

	/**
	 * @method
	 * @override
	 * @return	{String}
	 */
	app.toString = function() {
		return '[ChatDisplay object]';
	};

	//--------------------------------------------------------------------------
	//
	//  Private methods
	//
	//--------------------------------------------------------------------------

	var updateState = function() {
		switch ( _state ) {

			case 'login':
				construct_login();
				break;

			case 'wait':
				construct_wait();
				break;

			case 'content':
				if ( _chatData ) {
					construct_content();
					break;
				}
			default:
				this.innerHTML = '';
				break;

		}
		if ( updateSize ) updateSize();
	};

	//------------------------------------------------
	//  resize methods
	//------------------------------------------------

	// запускаем механихм авторесайзаа
	// инитиализация css-классов chatAutoContainer
	if ( !opera && !msie ) {
		/**
		 * @private
		 * @param	{String}	className
		 */
		var getElementsByClassName = ( browser.getMSIE()
			?	function(className) {
					var	arr = doc.getElementsByTagName( '*' ),
						l = arr.length,
						i, o,
						result = new Array(),
						pattern = new RegExp( '\\b' + className + '\\b' );
					for ( i=0; i<l; i++ ) {
						o = arr[i];
						if ( pattern.test( o.className ) ) {
							result.push( o );
						}
					}
					return result;
				}
			:	function(className) {
					return doc.getElementsByClassName( className );
				}
		);
		/**
		 * @private
		 */
		var updateSize = function() {
			var	arr = getElementsByClassName( 'chatAutoContainer' ),
				l = arr.length,
				i, s, p;
			for ( i=0; i<l; i++ ) {
				s = arr[ i ].style;
				s.width = '0';
				s.height = '0';
			}
			for ( i=0; i<l; i++ ) {
				p =	arr[ i ].parentNode;
				s =	arr[ i ].style;
				s.width =	( p.clientWidth - 2 ) + 'px';
				s.height =	( p.clientHeight - 2 ) + 'px';
			}
		};
		dom.addEventListener( win, RESIZE, updateSize );

	}

	//------------------------------------------------
	//  common methods
	//------------------------------------------------

	/**
	 * @private
	 */
	var onfocus = function() {
		this.blur();
	};

	/**
	 * @private
	 * @param	{Element}	target
	 * @param	{String}	text
	 */
	var appendText = function(target, text) {
		target.appendChild( doc.createTextNode( text ) )
	};

	/**
	 * @private
	 * @param	{Element}	target
	 */
	var clearNode = function(target) {
		while( target.lastChild ) target.removeChild( target.lastChild );
	};

	//------------------------------------------------
	//  get content methods
	//------------------------------------------------

	/**
	 * @private
	 * @return	{HTMLElement}
	 */
	var getChannelList = function() {
		return doc.getElementById( 'chatChannelList' );
	};

	/**
	 * @private
	 * @return	{HTMLElement}
	 */
	var getChannelSelect = function() {
		return doc.getElementById( 'chatChannelSelect' );
	};

	/**
	 * @private
	 * @return	{HTMLElement}
	 */
	var getCharacterList = function() {
		return doc.getElementById( 'chatCharacterList' );
	};

	/**
	 * @private
	 * @return	{HTMLElement}
	 */
	var getMessageList = function() {
		return doc.getElementById( 'chatMessageList' );
	};

	/**
	 * @private
	 * @return	{HTMLElement}
	 */
	var getInput = function() {
		return doc.getElementById( 'chatInput' );
	};

	//------------------------------------------------
	//  content methods
	//------------------------------------------------

	/**
	 * @private
	 * @param	{String}	name
	 * @param	{Boolean}	prv
	 */
	var characterToMessage = function(name, prv) {
		var input = getInput();
		if ( !input ) return;
		input.focus();
		if ( !name ) return;
		var	mg = input.value,
			expr = new RegExp( '\\s*(private|to)\\s*\\[\\s*' + name.replace( _regs, '\\$&' ) + '\\s*\\]\\s*', 'ig' ),
			arr,
			j = 0,
			text = '';
		while ( arr = expr.exec( mg ) ) {
			prv = ( arr[ 1 ].toLowerCase() == 'to' );
			text += mg.substring( j, expr.lastIndex - arr[ 0 ].length ) + ( text.length == 0 ? '' : ' ' );
			j = expr.lastIndex;
		}
		text += mg.substr( j );

		text = ( prv ? 'private' : 'to' ) + ' [' + name + '] ' + text;
		input.value = text;
	};

	/**
	 * @private
	 * @method
	 * @param	{HTMLElement}	e
	 * @param	{String}		target
	 * @param	{Boolean}		prv
	 */
	var activeCharacterLink = function(e, target, prv) {
		e.onclick = function(event) {
			characterToMessage( target, prv );
			return false;
		};
		
	};

	/**
	 * @private
	 * @method
	 * @param	{avangard.chat.Message}		message
	 * @return	{HTMLDivElement}
	 */
	var createMessage = function(message) {
		switch ( message.type ) {
			case 1:		return createRawMessage( message );
			default:	return createSimpleMessage( message );
		}
	};

	/**
	 * @private
	 * @method
	 * @param	{avangard.chat.Message}		message
	 * @return	{HTMLDivElement}
	 */
	var createRawMessage = function(message) {
		var e = tpl_message( 'raw' );
		e.innerHTML = message.text;
		return e;
	};

	/**
	 * @private
	 * @method
	 * @param	{avangard.chat.Message}		message
	 * @return	{HTMLDivElement}
	 */
	var createSimpleMessage = function(message) {
		
		// сформируем сообщение
		var	content = tpl_messageContent();

		// делаем специфические замены
		var	text =	message.text,
			r,
			i,
			j =		0,
			l =		text.length,
			n, c,
			s =		0,
			arr,
			prv = false,
			to = false,
			selfName = _chatData.characterName.toLowerCase(),
			fromSelf = ( message.sender && message.sender.toLowerCase() == selfName ),
			toSelf;
		for ( i=0; i<l; i++ ) {
			switch ( text.charAt( i ) ) {
				case 't':	// to
				case 'T':
					if ( text.charAt( i + 1 ).toLowerCase() == 'o' ) {
						arr = _nick.exec( text.substr( i + 2 ) );
						if ( arr ) {
							n = arr[1];
							toSelf = ( n.toLowerCase() == selfName );
							if ( fromSelf || toSelf ) to = true;
							c = 2 + arr[0].length;
							r = tpl_characterTo( n );
							activeCharacterLink( r, ( message.sender && toSelf ? message.sender : n ) );
						}
					}
					break;
				case 'p':	// private
				case 'P':
					if ( text.substr( i + 1, 6 ).toLowerCase() == 'rivate' ) {
						arr = _nick.exec( text.substr( i + 7 ) );
						if ( arr ) {
							n = arr[1];
							toSelf = ( n.toLowerCase() == selfName );
							if ( ( fromSelf || toSelf ) && message.type == 2 ) prv = true;
							c = 7 + arr[0].length;
							r = tpl_characterPrivate( n );
							activeCharacterLink( r, ( message.sender && toSelf ? message.sender : n ), true );
						}
					}
					break;
				case 'h': // http://
				case 'H':
					if ( text.substr( i + 1, 6 ).toLowerCase() == 'ttp://' ) {
						arr = _uri.exec( text.substr( i + 7 ) );
						if ( arr ) {
							c = 7 + arr[ 0 ].length;
							r = tpl_link( 'http://' + arr[0] );
						}
					}
					break;
				case ';':
				case ':':
					c = 1;
					if ( _smile2.test( text.charAt( i + 1 ) ) ) c++;
					if ( _smile3.test( text.charAt( i + c ) ) ) {
						c++;
						r = tpl_textSmile( text.substr( i, c ) );
					} else if ( s < 3 ) {
						c = text.indexOf( ':', i + 1 );
						if ( c > 0 ) {
							r = tpl_smile( text.substring( i + 1, c ) );
							if ( r ) {
								c -= i - 1;
								s++;
							}
						}
					}
					break;
				case '%':
				case '=':
					if ( _smile3.test( text.charAt( i + 1 ) ) ) {
						c = 2;
						r = tpl_textSmile( text.substr( i, c ) );
					}
					break;

			}
			if ( r ) {
				if ( j != i ) {
					appendText( content, text.substring( j, i ) );
				}
				content.appendChild( r );
				j = i + c;
				i += c - 1;
				r = null;
			} else { // ничего не нашли. надо найти начало слова
				_spaces.lastIndex = i;
				i = ( _spaces.exec( text ) ? _spaces.lastIndex - 1 : l );
			}
		}
		appendText( content, text.substring( j, i ) );

		// создаём сообщение
		var e = tpl_message( message.sender ? ( prv ? 'private' : ( to ? 'to' : '' ) ) : 'system' );

		// время
		r = tpl_messageTime( message.sendTime );
		r.onclick = function() {
			if ( _selectedChannel ) {
				var e = new EEvent( 'messageSelect' );
				e.channel = _selectedChannel.id;
				e.message = message;
				app.dispatcher.dispatchEvent( e );
			}
			return false;
		};
		e.appendChild( r );
		appendText( e, ' ' );

		// отправитель
		if ( message.sender ) {
			r = tpl_characterSender( message.sender );
			activeCharacterLink( r, message.sender, prv );
			e.appendChild( r );
			appendText( e, ' ' );
		}

		e.appendChild( content );

		return e;
	};

	//------------------------------------------------
	//  construct methods
	//------------------------------------------------

	/**
	 * @private
	 */
	var	construct_login = function() {
		app.innerHTML = tpl_retry( null, 'chatLoginForm' );

		var	_loginForm =	doc.getElementById( 'chatLoginForm' );

		// подписываем на отправку формы
		// инитиализируем _loginForm
		dom.addEventListener(
			_loginForm,
			'submit',
			function() {
				app.dispatcher.dispatchEvent( new EEvent( 'login' ) );
				return false;
			}
		);

	};

	/**
	 * @private
	 */
	var construct_wait = function() {
		app.innerHTML = tpl_wait( 'Подключение к серверу...' );
	};

	/**
	 * @private
	 */
	var construct_content = function() {
		app.innerHTML = tpl_content();

		var	_channelList =				getChannelList(),
			_channelSelect =			getChannelSelect(),
			_characterList =			getCharacterList(),
			_messageListContainer =		doc.getElementById( 'chatMessageListContainer' ),
			_messageList =				getMessageList(),
			_form =						doc.getElementById( 'chatForm' ),
			_input =					getInput();

		// подписываем на отправку формы
		// инитиализируем _form
		dom.addEventListener(
			_form,
			'submit',
			function() {
				if ( _selectedChannel ) {
					if ( !_empty.test( _input.value ) ) {
						var e = new EEvent( 'input' );
						e.channel = _selectedChannel.id;
						e.message = _input.value;
						app.dispatcher.dispatchEvent( e );
					}
					_input.value = '';
				}
				return false;
			}
		);

		// создаём список пользователей
		// инитиализация _characterList
		( function() {
			if ( !_characterList ) return;
			// vars
			var	_data =			null,
				_hash =			new Object(),
				_list_name =	new Array(),
				_list_e =		new Array();
			/**
			 * @private
			 * @param	{String}	name
			 * @return	{String}
			 */
			var _validateCharacterName = function(name) {
				return name.toUpperCase();
			};
			/**
			 * @private
			 * добавляем персонажа
			 * @param	{avangard.Character}	character
			 */
			var _addCharacter = function(character) {
				var e = tpl_character( character );
				e.getData = function() {
					return character;
				};
				e.update = function() {
					_removeCharacter( character );
					_addCharacter( character );
				};
				character.addEventListener( UPDATE, e, UPDATE );

				var	l =		_list_name.length,
					name =	_validateCharacterName( character.name ),
					i;
				if ( name <= '~' ) { // вероятность того что элемент находит в начале списка выше
					for ( i=0; i<l; i++ ) {
						if ( _list_name[ i ] > name ) break;
					}
				} else {
					for ( i=l-1; i>=0; i-- ) {
						if ( _list_name[ i ] < name ) break;
					}
					i++;
				}
				_hash[ character.id ] = e;
				if ( i == l ) {
					_list_name.push( name );
					_list_e.push( e );
					_characterList.appendChild( e );
				} else {
					_list_name.splice( i, 0, name );
					_list_e.splice( i, 0, e );
					_characterList.insertBefore( e, _list_e[ i + 1 ] );
				}
                //jQuery('#chatCharacterListScroll').jScrollPane({showArrows:true, scrollbarWidth: 15}); 
                
			};
			/**
			 * @private
			 * удаляем персонажа
			 * @param	{avangard.Character}	character
			 */
			var _removeCharacter = function(character) {
				var	e =		_hash[ character.id ],
					name =	_validateCharacterName( character.name ),
					i =		( name <= '~'
								? utils.indexOf( _list_name, name )
								: utils.lastIndexOf( _list_name, name )
							);
				delete _hash[ character.id ];
				_list_name.splice( i, 1 );
				_list_e.splice( i, 1 );
				_characterList.removeChild( e );
				character.removeEventListener( UPDATE, e, UPDATE );
                //jQuery('#chatCharacterListScroll').jScrollPane({showArrows:true, scrollbarWidth: 15});
			};
			/**
			 * @private
			 * обновление всего списка
			 */
			var _update = function() {
				var	arr = _data.getCharacters(),
					i,
					c,
					hash =	new Object();
				for ( i in arr ) {
					c = arr[ i ];
					if ( !_hash[ c.id ] ) {
						_addCharacter( c );
					}
					hash[ c.id ] = true;
				}
				for ( i in _hash ) {
					if ( !hash[ i ] ) {
						_removeCharacter( _hash[ i ].getData() );
					}
				}
			};
			/**
			 * @private
			 * очистка списка персонажей
			 */
			var _clear = function() {
				for ( var i in _hash ) {
					_removeCharacter( _hash[ i ].getData() );
				}
			};
			/**
			 * @method
			 * @return	{avangard.chat.Channel}
			 */
			_characterList.getData = function() {
				return _data;
			};
			/**
			 * @method
			 * @param	{avangard.chat.Channel}	value
			 */
			_characterList.setData = function(value) {
				if ( _data === value ) return;
				if ( _data ) {
					_data.removeEventListener( E_A,	this, HE_A );
					_data.removeEventListener( E_R,	this, HE_R );
					_clear();
				}
				_data = value;
				if ( _data ) {
					_data.addEventListener( E_A,	this, HE_A );
					_data.addEventListener( E_R,	this, HE_R );
					_update();
				}
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_characterList.handler_added = function(event) {
				_addCharacter( event.character );
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_characterList.handler_removed = function(event) {
				_removeCharacter( event.character );
			};
		}() );


		// создаём табпанель со списком каналов
		// инитиализируем _channelList
		( function() {
			if ( !_channelList ) return;
			// vars
			var	_data =			null,
				_hash =			new Object(),
				_list_data =	new Array(),
				_list_e =		new Array(),
				_sChannel =		null;
			/**
			 * @private
			 * добавляем канал
			 * @param	{avangard.chat.Channel}		channel
			 */
			var _addChannel = function(channel) {

				var e = tpl_channel( channel );
				e.getData = function() {
					return channel;
				};
				e.handler_added = function() {
					if ( channel === _selectedChannel ) return;
					this.setStatus( 'unread' );
				};
				e.onclick = function() {
					app.setChannel( channel );
					return false;
				};
				channel.logger.addEventListener( E_A, e, HE_A );

				var	l = _list_data.length,
					i, d;
				for ( i=0; i<l; i++ ) {
					d = _list_data[ i ];
					if (
						channel.type < d.type || (
							channel.type == d.type &&
							channel.id < d.id
						)
					) break;
				}
				_hash[ channel.id ] = e;
				if ( i == l ) {
					_list_data.push( channel );
					_list_e.push( e );
					_channelList.appendChild( e );
				} else {
					_list_data.splice( i, 0, channel );
					_list_e.splice( i, 0, e );
					_channelList.insertBefore( e, _list_e[ i + 1 ] );
				}
				if ( _channelSelect ) _channelSelect.select( channel ); // выделяем наш канал
				if ( channel === _selectedChannel ) { // если наш канал назначин смотрящим, то сделаем его таковым
					_channelList.select( channel );
				} else if ( _channelSelect ) {
					_channelSelect.select( _selectedChannel ); // вернём обратно выделение списка
				}
			};
			/**
			 * @private
			 * удаляем канал
			 * @param	{avangard.chat.Channel}		channel
			 */
			var _removeChannel = function(channel) {
				var	e =	_hash[ channel.id ],
					i =	utils.indexOf( _list_data, channel );
				delete _hash[ channel.id ];
				_list_data.splice( i, 1 );
				_list_e.splice( i, 1 );
				_channelList.removeChild( e );
				channel.logger.removeEventListener( E_A, e, HE_A );
			};
			/**
			 * @private
			 * обновление всего списка
			 */
			var _update = function() {
				var	arr = _data.getValues(),
					i,
					c,
					hash =	new Object();
				for ( i in arr ) {
					c = arr[ i ];
					if ( !_hash[ c.id ] ) {
						_addChannel( c );
					}
					hash[ c.id ] = true;
				}
				for ( i in _hash ) {
					if ( !hash[ i ] ) {
						_removeChannel( _hash[ i ].getData() );
					}
				}
			};
			/**
			 * @private
			 * очистка списка с каналами
			 */
			var _clear = function() {
				for ( var i in _hash ) {
					_removeChannel( _hash[ i ].getData() );
				}
			};
			/**
			 * @method
			 * @return	{controller.utils.Hash}
			 */
			_channelList.getData = function() {
				return _data;
			};
			/**
			 * @method
			 * @param	{controller.utils.Hash}	value
			 */
			_channelList.setData = function(value) {
				if ( _data === value ) return;
				if ( _data ) {
					_data.removeEventListener( E_A, this, HE_A );
					_data.removeEventListener( E_R, this, HE_R );
					_clear();
				}
				_data = value;
				if ( _data ) {
					_data.addEventListener( E_A, this, HE_A );
					_data.addEventListener( E_R, this, HE_R );
					_update();
				}
			};
			/**
			 * @method
			 * @param	{avangard.chat.Channel}	data
			 * @return	{Element}
			 */
			_channelList.getChannel = function(data) {
				return _hash[ data.id ];
			};
			/**
			 * @method
			 * @param	{avangard.chat.Channel}	data
			 */
			_channelList.select = function(data) {
				if ( data === _sChannel ) return;
				var e;
				if ( _sChannel ) {
					e = _hash[ _sChannel.id ];
					if ( e ) e.setStatus();
				}
				_sChannel = data;
				if ( _sChannel ) {
					e = _hash[ _sChannel.id ];
					if ( e ) e.setStatus( 'active' );
				}
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_channelList.handler_added = function(event) {
				var channel = event.value;
				_addChannel( channel );
				app.setChannel( channel );
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_channelList.handler_removed = function(event) {
				_removeChannel( event.value );
			};
		}() );

		// создаём комбобокс со списком каналов
		// инитиализируем _channelSelect
		( function() {
			if ( !_channelSelect ) return;
			// vars
			var	_data =		null,
				_hash =		new Object(),
				_count =	0,
				_style =	_channelSelect.style;
			/**
			 * @private
			 * добавляем канал
			 * @param	{avangard.chat.Channel}		channel
			 */
			var _addChannel = function(channel) {
				var e = doc.createElement( 'option' );
				e.value = channel.id;
				appendText( e, channel.name );
				_hash[ channel.id ] = e;
				_channelSelect.appendChild( e );
				if ( _count == 1 ) {
					_style.visibility = 'inherit';
				}
				_count++;
				if ( _chatData.channels.has( channel.id ) ) {
					_channelSelect.select( channel );
				}
			};
			/**
			 * @private
			 * удаляем канал
			 * @param	{avangard.chat.Channel}		channel
			 */
			var _removeChannel = function(channel) {
				var	e =	_hash[ channel.id ];
				delete _hash[ channel.id ];
				_channelSelect.removeChild( e );
				_count--;
				if ( _count == 1 ) {
					_style.visibility = 'hidden';
				}
			};
			/**
			 * @private
			 * обновление всего списка
			 */
			var _update = function() {
				_clear();
				var	l = _data.getLength(),
					i;
				for ( i=0; i<l; i++ ) {
					_addChannel( _data.getAt( i ) );
				}
			};
			/**
			 * @private
			 * очистка списка с каналами
			 */
			var _clear = function() {
				for ( var i in _hash ) {
					delete _hash[ i ];
				}
				clearNode( _channelSelect );
				_count = 0;
				_style.visibility = 'hidden';
			};
			/**
			 * @method
			 * @return	{controller.utils.List}
			 */
			_channelSelect.getData = function() {
				return _data;
			};
			/**
			 * @method
			 * @param	{controller.utils.List}	value
			 */
			_channelSelect.setData = function(value) {
				if ( _data === value ) return;
				if ( _data ) {
					_data.removeEventListener( E_A, this, HE_A );
					_data.removeEventListener( E_R, this, HE_R );
					_clear();
				}
				_data = value;
				if ( _data ) {
					_data.addEventListener( E_A, this, HE_A );
					_data.addEventListener( E_R, this, HE_R );
					_update();
				}
			};
			/**
			 * @method
			 * @param	{avangard.chat.Channel}	data
			 */
			_channelSelect.select = function(data) {
				if ( data && data.id in _hash ) {
					_hash[ data.id ].selected = true;
				}
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_channelSelect.handler_added = function(event) {
				_addChannel( event.value );
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_channelSelect.handler_removed = function(event) {
				_removeChannel( event.value );
			};
			// подписываемся на изменения
			dom.addEventListener(
				_channelSelect,
				'change',
				function() {
					var channel = parseInt( _channelSelect.value );
					if ( _selectedChannel && channel == _selectedChannel.id ) return;
					var e = new EEvent( 'channelSelect' );
					e.channel = channel;
					app.dispatcher.dispatchEvent( e );
				}
			);
		}() );

		// создаём список сообщений
		// инитиализация _messageList
		( function() {
			if ( !_messageList ) return _messageList;
			// vars
			var	_data =		null,
				_hash =		new Object(),
				_list =		new Array(),
				_toScroll =	true;
			/**
			 * @private
			 */
			var	_scroll = function() {
				if ( _toScroll ) {
					_toScroll = false;
					setTimeout( _doScroll, 10 );
				}
			};
			/**
			 * @private
			 */
			var	_doScroll = function() {
				_toScroll = true;
				_messageList.scrollTop = _messageList.scrollHeight - _messageList.clientHeight;
                //jQuery('#chatMessageListScroll').jScrollPane({showArrows:true, scrollbarWidth: 15});
                //jQuery('#chatMessageListScroll')[0].scrollTo(_messageList.scrollHeight)
			};
			/**
			 * @private
			 * добавляем сообщение
			 * @param	{avangard.chat.Message}		message
			 */
			var	_addMessage = function(message) {
			//console.log("_addMessage");
				var e = createMessage( message );
				_hash[ message.id ] = e;
				_list.push( e );
				_messageList.appendChild( e );
				while ( _list.length > _data.getLength() ) {
					_messageList.removeChild( _list.shift() );
				}
			};
			/**
			 * @private
			 * удаляем сообщение
			 * @param	{avangard.chat.Message}		message
			 */
			var _removeMessage = function(message) {
				var	e =	_hash[ message.id ],
					i =	utils.indexOf( _list, e );
				delete _hash[ message.id ];
				_list.splice( i, 1 );
				_messageList.removeChild( e );
			};
			/**
			 * @private
			 * обновление всего списка
			 */
			var _update = function() {
				_clear();
				var	arr =	_data.getValues(),
					i,
					l =		arr.length;
				for ( i=0; i<l; i++ ) {
					_addMessage( arr[ i ] );
				}
				_scroll();
			};
			/**
			 * @private
			 * очистка списка
			 */
			var _clear = function() {
				if ( _list.length <= 0 ) return;
				for ( var i in _list ) {
					_messageList.removeChild( _list[ i ] );
				}
				_list.splice( 0, _list.length );
				for ( i in _hash ) {
					delete _hash[ i ];
				}
			};
			/**
			 * @method
			 * @return	{controller.Logger}
			 */
			_messageList.getData = function() {
				return _data;
			};
			/**
			 * @method
			 * @param	{controller.Logger}	value
			 */
			_messageList.setData = function(value) {
				if ( _data === value ) return;
				if ( _data ) {
					_data.removeEventListener( E_A, this, HE_A );
					_data.removeEventListener( E_R, this, HE_R );
					_clear();
					dom.removeEventListener( win, RESIZE, _scroll );
				}
				_data = value;
				if ( _data ) {
					_data.addEventListener( E_A, this, HE_A );
					_data.addEventListener( E_R, this, HE_R );
					_update();
					dom.addEventListener( win, RESIZE, _scroll );
				}
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_messageList.handler_added = function(event) {
				var s = ( _messageList.scrollTop == _messageList.scrollHeight - _messageList.clientHeight );
				_addMessage( event.value );
				if ( s ) _scroll();
			};
			/**
			 * @private
			 * @param	{controller.events.Event}	event
			 */
			_messageList.handler_removed = function(event) {
				_removeMessage( event.value );
			};
		}() );

		// init
		if ( _chatData ) {
			if ( _channelList )		_channelList.setData( _chatData.channels );
			if ( _channelSelect )	_channelSelect.setData( _chatData.channelHeaders );
		}
		if ( _selectedChannel ) {
			if ( _channelList )		_channelList.select( _selectedChannel );
			if ( _channelSelect )	_channelSelect.select( _selectedChannel );
			if ( _characterList )	_characterList.setData( _selectedChannel );
			if ( _messageList )		_messageList.setData( _selectedChannel.logger );
		}

	};

	return app;

} );